5W - 이스티오 mTLS와 SPIFFE
개요
이스티오와 같은 서비스 메시에서는 여러 독립된 컴포넌트들이 상호작용을 해야 하며, 그렇기 때문에 다양한 보안적 고려 요소가 생긴다.
일단 통신 간 암호화는 거의 필수 고려 사항이고, 통신 주체 간 인증, 트래픽 인가 등의 요소도 빼놓을 수 없다.
이스티오에서는 관련된 커스텀 리소스와 기능들을 제공하고 있다.
이번 노트에서는 먼저 통신 간 암호화, 그리고 tls 통신 간 신원을 설정하는 Peer Authentication 리소스를 먼저 알아볼 것이다.
이 노트를 편하게 읽기 위해서는 mTLS, X.509가 무엇인지는 알고 있어야 한다.
사전 지식
위 그림은 각 프록시가 X.509 인증서를 받아서 서로 mTLS 통신을 하는 흐름을 간단하게 담고 있다.
이번 문서에서는 이 내용을 집중적으로 다룰 것이다.
이스티오 메시 속에서 모든 서비스는 고유한 신원을 받는다.
이것은 X.509 인증서를 기반으로 이뤄지며, 이 인증서는 서비스의 신원이자 통신 간 암호화의 수단이 되기도 한다.
이 인증서를 발급하는 주체는 istiod로, 자신의 개인키를 이용해 서명한 인증서를 각 서비스에게 전달한다.
istiod에서는 각 서비스가 인증서 서명 요청(CSR)을 할 수 있도록 gRPC 서비스를 노출하고 있다(과거 Citadel).
데이터 플레인 사이드카가 배치될 때, pilot-agent는 개인키와 CSR을 만들어 istiod로부터 인증서를 발급받는다.
엔보이가 가동되면 엔보이는 에이전트가 노출한 소켓 파일에 SDS(Secret Discovery Service) api를 통해 인증서와 개인키를 가져와 세팅한다.
에이전트는 인증서의 서명을 주기적으로 체크하며 필요 시 갱신하는 작업을 수행한다.
SPIFFE
구체적으로 인증서를 발급하는 과정을 알아보기 전에 간단하게 SPIFFE에 대해 알아본다.
Secure Production Identity Framework For Everyone.
다양한 소프트웨어 시스템 환경에서 동적으로, 일관되게 보안적으로 고유한 신원을 제공하기 위해 개발된 오픈소스 표준이다.
이스티오는 이 표준을 바탕으로 인증서 발급 아키텍처를 구축하고 있다.
온프레미스를 넘어 다양한 클라우드 환경이 나오면서 이질적이고 다양한 환경에서 고유한 신원을 제공하고자 하는 수요가 발생했다.
그래서 스피페는 구체적으로 여러 복합적인 환경에서 워크로드에 고유한 신원을 제공하는 것을 목표로 개발됐다.
이때 워크로드는 단일 목적을 가진 소프트웨어의 집합을 말한다.
즉 같은 역할을 하는 여러 웹서버는 하나의 워크로드라고 친다는 것이다.
워크로드 참고.
워크로드의 신원을 부여하기 위해서는 단순히 네트워크 식별자를 사용할 수는 없다.
여기에 실행되는 인스턴스의 고유한 정보를 이용하는 것도 불가능한데, 하나의 워크로드여도 여러 개의 복제본이 다양한 노드에서 실행될 수 있기 때문이다.
이를 위해 스피페에서는 자신만의 특별한 방법을 마련했는데..
SPIFFE ID
그것은 바로 스피페 ID이다.
스피페 ID는 URI의 한 종류로서 네트워크 환경에서 마음대로 지정할 수 있는 하나의 스킴이다.
spiffe://example.org/ns/default/sa/backend
스피페 id는 이렇게 생겼다.
조금 더 구체적인 형식을 보자.
spiffe://{신뢰 도메인}/{워크로드 식별자}
신뢰 도메인(trust domain)은 스피페를 구축한 인프라 전체를 아우르는 도메인을 말한다.
쿠버네티스라면 보통 클러스터 도메인(cluster.local)을 말한다.
같은 도메인으로부터 신원 증명을 발급 받은 모든 워크로드는 같은 도메인으로부터 검증될 수 있다.
워크로드 식별자 부분은 스피페를 구현하는 개체가 마음대로 정할 수 있다.
가령 위에 쿠버네티스 환경에서는 ns/{네임스페이스}/sa/{서비스어카운트}
라고 지정한 것처럼 말이다.
참고로, 스피페 ID는 워크로드에 대해 부여되는 게 기본이고 이게 워크로드에서 활용되지만 이를 발급 받는 과정을 위해 노드에 대해서도 부여되긴 한다.
SVID(Spiffe Verifiable Identity Document)
위 스피페 id가 식별자로 쓰이긴 할 텐데, 도대체 어떻게 이걸 통신 간에 적용할 것인가?
스피페에서는 워크로드가 자신의 신원을 증명할 수 있는 증명서를 SVID라는 개념으로 추상화시켰다.
추상화됐다고 하는 이유는 SVID 자체가 어떤 특정한 증명서 형식인 게 아니라 기존에 존재하던 형식을 활용하여 구성되기 때문이다.
스피페 id는 다음의 두 신원 증명 수단으로 제작될 수 있다.
스피페 id가 쏙 들어간 저런 형식들을 그냥 SVID라 부르는 것이다.
그러니 앞으로 SVID를 발급받는다 하면 저것 중에 하나 발급받는 거겠거니 생각하면 된다.
x509의 경우 그냥 이렇게 X509v3 Subject Alternative Name
하위로 URI로 spiffe ID가 들어가는 식이다.
이 사이트에 들어가서 x509 인증서를 그대로 갖다 박으면 SVID의 정보를 예쁘게 확인할 수 있게 해준다.
Workload API
SVID라는 것은 결국 흔히 통신 간 신원을 증명하는 수단으로 활용되는 인증서나 토큰에 추가적인 정보가 들어간 형태일 뿐이라는 것인데, 그렇다면 이러한 인증서나 토큰을 어떻게 발급받을 것이냐가 마지막 관건이 된다.
지금까지의 개념을 보자면, 결국 워크로드가 어디로부턴가 SVID를 받아서 활용해야 한다.
스피페에서는 이 방식을 다음과 같은 흐름으로 정리했다.
일단 워크로드는 신뢰 도메인을 기반으로 인증서를 발급해주는 중앙 서버로부터 SVID를 받아야 하는 상황이다.
- 워크로드는 서버에 바로 요청을 하지 않고 자신 노드에 위치한 에이전트에 요청을 한다.
- 이때 워크로드가 에이전트에 보낼 수 있도록 정의된 인터페이스가 바로 Workload API이다.
- 에이전트는 현재 노드의 정보를 식별하고, 요청한 워크로드를 식별하여 서버로부터 고유한 SVID를 받아와 공급한다.
즉, 워크로드는 에이전트 - 서버를 거쳐서 SVID를 받게 되는 건데 이러한 방식은 워크로드가 스피페의 세부 구현 여부를 몰라도 되도록 추상화한다는 이점이 있다.
스피페는 다양하고 이질적인 환경에서도 통일된 신원을 제공하기 위한 프레임워크라고 했다.
이때 환경마다 복잡하고 세분화될 수 있는 각종 신원 마련 수단을 워크로드 입장에서는 몰라도 되도록 구조를 잡았고, 이제 스피페를 활용하려는 조직에서는 이 워크로드 API에 대해서만 알고 관련한 구현을 하면 된다.
참고로 워크로드 api는 gRPC로 정의된다고 한다.[1]
SPIFFE를 조금 더 깊게 이해하려면 스피페를 만든 팀이 구현한 아키텍쳐인 SPIRE를 자세히 보면 도움이 된다.
여기에서 더 깊게 다루진 않겠다.
이스티오 인증서 발급 과정
다시 돌아와서, 이스티오는 위 스피페의 표준에 따라 인증서를 발급하는 아키텍처를 구축했다.
그래서 여기에서 발급되는 인증서는 SVID라고 하는, SPIFFE 양식을 따르는 인증서이다.
이스티오 환경에서 스피페 방식을 준수하고 따르는 것에는 다음의 이점이 있다.
- 중앙 집중형 신원 관리
- 자동 발급과 갱신 가능
그렇다면 구체적으로 어떻게 이스티오에서는 인증서를 발급받고 이를 활용하는가?
스피페에서 지정한 SPIRE의 방식과 살짝 비슷하면서도 상당히 다르다.
- 파드가 배치될 때 쿠버 API 서버는 서비스 어카운트 토큰을 마운팅한다.
- 조금 더 구체적으로는 파드가 배치된 노드의 kubelet이 api 서버에 토큰 생성 요청을 날리고 받아와 마운팅.
- 이 토큰은 사이드카가 주입될 당시 istiod에서 Admission Webhook으로
istio-ca
가 청중이도록 만든 토큰이다. - 토큰은
/var/run/secrets/tokens/istio-token
에 저장되며, 현재 파드가 할당받은 서비스 어카운트의 정보를 가진다.
- 파일럿 에이전트는 이 정보를 바탕으로 istiod에 인증서 발급 요청(CSR)을 보낸다.
- 직접 비대칭키를 만든다.
- 위 토큰의 정보를 통해
spiffe://cluster.local/ns/{네임스페이스}/sa/{서비스어카운트}
형식의 SPIFFE id를 만든다. - SPIFFE id를 X509 확장 필드에 넣어서 인증서 요청문을 만든다.
- 토큰과 함께 CSR을 istiod에 보낸다.
- istiod는 들어온 토큰에 대해 api서버에 TokenReview API 요청을 보내 신원을 검증한다.
- 신원이 확인되면 istiod는 자신의 키를 이용해 인증서에 서명하고 에이전트에 돌려준다.
- 에이전트는 유닉스 도메인 소켓을 열어 리스닝하고, 엔보이에 SDS api 경로가 이쪽을 향하도록 세팅하여 가동한다.
- 이를 통해 엔보이에 개인키와 인증서가 전달된다.
서비스 어카운트가 워크로드 식별자라는 것은 곧 서비스 어카운트가 워크로드의 단위라는 것이다.
그런데 SPIRE에서는 워크로드 증명 과정이 에이전트 단계에서 끝나는데, 이스티오에서는 istiod가 토큰 리뷰 요청을 날린다!
이스티오를 기준으로 SPIRE와의 몇 가지 차이를 정리하자면,
- 노드에 대한 개념이 없다.
- (다만 Kubernetes v1.33 - Octarine에서는 노드에 토큰을 발급할 수 있도록 서비스 어카운트의 개념이 확장되고 있는데, 추후에 이러한 사항이 반영될 수도 있다.)
- 서버(istiod)가 SPIFFE id를 미리 생성하고 엔트리로 등록하고 있지도 않다.
- SPIRE에서는 무조건 서버에서 내리 전달 방식을 채택한다.
- 워크로드 증명과정에 서버가 들어간다.
- 마치 노드 증명 과정과 워크로드 증명과정이 짬뽕된 느낌이다.
실제로 해당 신원을 사용하는 워크로드인 엔보이는 발급 과정에 관여하지 않으며 워크로드 별 고유한 신원을 제공한다는 기본 스피페의 철학에는 충실하기에 이스티오는 스피페의 구현체 중 하나로서 탄탄히 구실을 해내고 있다고 말할 수 있다.
참고로 SPIRE 오퍼레이터를 설치해서 이스티오의 모든 인증서 발급과 관리 과정을 SPIRE를 거치도록 하는 것도 가능하다.[2]
이 경우 엔보이의 SDS api가 설정을 받아갈 유닉스 도메인 소켓을 SPIFFE CSI 드라이버가 마운팅해준다.
그리고 기본 설계에 충실하게 에이전트는 노드 별로 배치된 채 증명서를 발급한다.
물론 SPIRE 서버가 워크로드들을 미리 알 수 있도록 등록을 자동화 추가적인 커스텀 리소스를 만들어주긴 해야 한다.
PeerAuthenticiation
아무튼 위의 방법으로 각 서비스는 인증서를 받게 된다.
엔보이는 이제 이 인증서를 통해 통신을 하게 되는데 이때 mTLS를 어떻게 할지에 대해 설정할 수 있는 리소스가 바로 PeerAuthentication이다.
참고로 이 리소스는 요청을 받는 엔보이의 리스너에 설정이 들어가는 것이기 때문에 mTLS에 대해 만약 클라 측에서 커스텀을 하고 싶다면 이스티오 데스티네이션룰을 설정해야 한다.
이스티오의 정책 관련 리소스가 대부분 그러한데, 정책의 적용 범위가 3가지로 나뉜다.
- 특정 네임스페이스에 셀렉터를 지정하면 지정된 워크로드 대상으로 적용.
- 특정 네임스페이스에 셀렉터를 명시하지 않으면 네임스페이스 전체 적용.
- 이스티오 루트 네임스페이스(istio-system)에 셀렉터를 명시하지 않으면 메시 전체 적용.
지역적인 설정일수록 우선순위가 더 높은 방식이고, 네임스페이스나 메시 단위 리소스는 해당 범위에 하나만 존재할 수 있다.
여러 개를 둔다고 해도 가장 오래된 것만 적용되는 방식이다.
워크로드 대상 정책 역시 여러 개가 매칭이 되는 케이스라면 오래된 정책이 적용된다.
그럼 어떤 식으로 작성하는가?
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: foo
spec:
selector:
matchLabels:
app: finance
mtls:
mode: STRICT
portLevelMtls:
8080:
mode: DISABLE
selector
로 워크로드를 선택하고, mtls
에서 tls 세팅, portLevelMtls
에서 포트 레벨 tls 설정을 한다.
(당연하지만 포트 레벨 tls는 서비스 포트가 아니라 워크로드 기준이다.)
tls 모드는 다음과 같다.
- UNSET - 설정되지 않은 상태로, 상위 범위의 리소스 정책이 적용되며 그 외엔 PERMISSIVE로 간주된다.
- DISABLE - tls 비활성화
- PERMISSIVE - mtls를 기본으로 하나 아니어도 허용
- STRICT - 무조건 mtls(클라 측에 무조건 인증서 세팅 필요)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: "example-workload-policy"
namespace: "foo"
spec:
selector:
matchLabels:
app: example-app
portLevelMtls:
80:
mode: DISABLE
UNSET의 예시를 들자면, 이건 워크로드의 80 포트에 대해서는 mtls를 비활성화한다.
그러나 foo
네임스페이스 단위의 정책이 따로 설정돼있다면 다른 포트들에 대해서는 해당 정책이 적용될 것이다.
mTLS
mTLS가 어떤 식으로 이뤄지는지 간단하게만 짚자면..
mutual TLS는 클라이언트의 신원도 인증서를 기반으로 검증하는 추가 단계가 포함된 프로토콜이다. 별도로 분리된 프로토콜은 아니고, 그냥 위의 동작 흐름 상에서 클라 인증서를 교환하는 과정이 조금 추가될 뿐이다. 일반 사용자가 브라우저를 이용할 때 사용되진 않고, 서버 간 통신을 할 때 서로를 신뢰하기 위한 수단으로 사용된다. mTLS는 서버의 신원만 확인하던 기존 tls와 달리 클라의 신원도 서버에서 요구한다. 이를 통해 서로 통신하는 상대가 누군지 정확하게 특정 가능하다는 특징이 있다. # 실습 진행 본격적으로 PeerAuthentication을 건드리는 실습을 해본다. 여기에 추가적으로 위에서 설명한 spiffe 인증서가 발급되는 과정으로 생긴 파일들도 확인해볼 것이다. ## 환경 세팅 ```yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: profile: default components: pilot: k8s: env: - name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG value: "true" ingressGateways: - name: istio-ingressgateway enabled: true k8s: service: type: NodePort ports: - name: http port: 80 targetPort: 8080 nodePort: 30000 - name: https port: 443 targetPort: 8443 nodePort: 30005 externalTrafficPolicy: Local meshConfig: outboundTrafficPolicy: mode: ALLOW_ANY enableTracing: true defaultProviders: metrics: - prometheus tracing: [] accessLogFile: /dev/stdout values: global: variant: distroless pilot: env: ENABLE_NATIVE_SIDECARS: true ``` 항상 세팅 방식은 동일하다. KIND 역시 마찬가지. ```sh # resolve domain resolve = "catalog.istioinaction.io:30000:127.0.0.1" resolve = "webapp.istioinaction.io:30000:127.0.0.1" resolve = "simpleweb.istioinaction.io:30000:127.0.0.1"silent
silent
대신 이번 실습부터는 로컬에서 curl을 날릴 때 조금 더 편하게 하기 위해 conf 파일을 만들었다.
```sh
curl -K curl.conf {주소}
이렇게 인자를 전달하면 여기에 사전 세팅한 내용들을 인자로 전달해서 curl을 날릴 수 있다.
/etc/hosts
에 도메인 매번 넣었다 빼기는 귀찮고, 그렇다고 curl에 매번 인자 길게 넣기 귀찮은 나 같은 사람에게 매우 유용한 방식이다!
k create ns istioinaction
k label ns istioinaction istio-injection=enabled
k ns istioinaction
항상 하던 대로 istioinaction 네임스페이스를 만들고..
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
kubectl apply -f ch9/sleep.yaml -n default
여태 책에서 나온 실습 예제를 넣어준다.
sleep 파드를 default에 배치하는데, 이건 서비스 메시가 적용되지 않은 네임스페이스에서의 통신 상황을 가정하는 것이다.
단순하게 통신을 날리는 용으로 띄운 파드로 netshoot을 쓰건 우분투를 쓰건 상관 없다.
default 네임스페이스는 사이드카 인젝션이 일어나지 않은 상황이다.
이때 sleep 파드의 통신을 제한하거나 통과시키는 설정을 해볼 것이다.
sleep 파드에서 통신이 날아가는 과정을 키알리로 간단하게 보자면..
watch 'kubectl -n default exec deploy/sleep -c sleep -- curl -s webapp.istioinaction/api/catalog -o /dev/null -w "%{http_code}\n"'
unknown으로부터 요청이 날아온다.
사이드카가 주입되지 않은 sleep 파드에서의 요청은 인증서가 없는 클라로부터의 통신이기에 unknown으로 표시된다.
peer authentication 리소스 활용
메시 범위 mtls 강제
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "istio-system"
spec:
mtls:
mode: STRICT
전역적으로 mtls를 강제해본다.
정책을 적용하자마자 위와 같이 요청에 에러가 발생한다.
이제 최소한 sleep 파드에서 통신을 하기 위해서는 자신도 인증서를 제출할 수 있어야만 한다.
로그를 뜯어보면 갑자기 NR(Non-Route)이라고 하면서 아예 트래픽이 막혀버리는 것을 확인할 수 있다.
워크로드 permissive 허용
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "webapp"
namespace: "istioinaction"
spec:
selector:
matchLabels:
app: "webapp"
mtls:
mode: PERMISSIVE
조금 더 지엽적인 설정이 우선 적용된다.
이번엔 webapp으로 가는 요청이 mtls를 희망하나 못 하더라도 그대로 요청이 성립되게 한다.
이제 webapp으로의 요청은 성공한다.
그러나 catalog로 직접 요청을 날려보면 이렇게 실패하는 것을 볼 수 있다.
istioctl pc listener webapp-7c96945758-9jzxt --port 15006 -ojson | fx
외부의 트래픽을 받는 포트는 15006이기 때문에 한번 확인해봤는데, tls가 강제되면 모든 필터체인 리스트에 transportSocket 필드가 생기며 filterChainMatch는 tls로 고정된다.
반면 permissive 모드라면 중간 필터 체인 중 하나는 위와 같이 ChainMatch에 raw_buffer로 표시되며, 이 필터에는 transportSocket 필드도 설정되지 않는다.
mtls가 설정된 필드를 조금 더 자세히 보면, 간단한 검증에 대한 설정이 들어간 게 보인다.
이때 재밌는 것이 바로 spiffe 기반으로 SAN을 매칭한다는 것이다.
PeerAuthN의 경우 클러스터 내부의 통신에 대한 설정이기 때문에 모든 적용 대상은 결국 SVID를 가지고 있을 것이고, 이를 기반으로 위 조건에서 걸리게 될 것을 짐작할 수 있다.
spiffe 인증서 확인
그럼 실제로 엔보이가 받은 인증서는 어떻게 생겼을까?
istioctl을 통해 쉽게 현재 프록시에 위치한 인증서 값을 꺼내볼 수 있다.
하나씩 꺼내보자.
istioctl pc secret debug -ojson | jq '.dynamicActiveSecrets[1].secret.validationContext.trustedCa.inlineBytes' -r | base64 -d | openssl x509 -text -noout
첫번째로 istiod의 루트 인증서를 꺼내본다.
그냥 별 볼일 없는 루트 인증서긴 하다.
istioctl pc secret debug -ojson | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | base64 -d | openssl x509 -noout -text
다음, 엔보이가 사용할 인증서를 꺼내보면 extensions 필드에 아주 흥미로운 값을 볼 수 있다!
X509v3 Subject Alternative Name
하위로 URI로 spiffe ID가 들어가고 있다.
현재 이 인증서는 cluster.locl이라는 신뢰 도메인 아래, istioinaction 네임스페이스의 default 서비스어카운트의 신원을 나타내는 SVID라는 것을 여기에서 확인할 수 있다.
이 사이트에 들어가서 x509 인증서를 그대로 갖다 박으면 SVID의 정보를 예쁘게 확인할 수 있게 해준다.
고마워요 텔포맨
func Verify() (bool, error) {
serverCert, err := os.ReadFile("x509/server.crt")
if err != nil {
panic(err)
}
rootCert, err := os.ReadFile("x509/root.crt")
if err != nil {
panic(err)
}
block, _ := pem.Decode(serverCert)
if block == nil {
return false, errors.New("failed to parse certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(rootCert)
if !ok {
return false, errors.New("failed to parse root certificate")
}
opts := x509.VerifyOptions{
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
fmt.Println(err)
}
return true, nil
}
고 언어를 공부하는 김에 간단하게 인증서 검사 함수를 만들었다.
이제 프록시가 생성될 때, pilot agent가 뱉는 로그를 조금 더 이해할 수 있다.
엔보이가 실행되기 전, 에이전트는 JWT 토큰을 먼저 읽는다.
그리고 CA인 istiod에 인증서를 요청한다.
그리고 자체적으로 SDS 서버를 기동하는데, 이건 그냥 var/run/secrets/workload-spiffe-uds/socket
소켓을 리스닝하는 프로세스이다.
엔보이는 SDS api를 이곳으로 날리게 될 것이다.
distroless 컨테이너 진입 후 확인 - 삽질
kubectl debug -it debug \
--image=nicolaka/netshoot \
--target=istio-proxy \
--share-processes \
--profile sysadmin \
-- zsh
아예 컨테이너 안 속으로 들어가서 정보를 조금 더 자세히 보도록 한다.
distroless 이미지를 쓰고 있는 관계로 네임스페이스를 공유하는 임시 컨테이너를 만들어 붙였다.
권한 문제로부터 자유롭게 돌아다녀야 하기 때문에 sysadmin의 권한을 주었다.
참고로 조금의 삽질을 하면서 방법을 찾아냈다.
처음 들어오면 기본적으로 네트워크 네임스페이스는 공유하고 있는 상태이다.
이 상태로 모든 정보를 뜯어볼 수 있으면 참 좋겠지만, 아쉽게도 파일시스템을 공유하고 있지 않은 상태이다.
nsenter -t 1
참고로 기본적으로 네임스페이스에 접속하는 명령어는 이건데, distroless 이미지의 경우에는 아예 /bin/sh
조차 없기 때문에 해당 명령이 먹히지 않았다.
그렇다고 방법이 없을쏘냐, 뚫으면 다 길이 있기 마련이다.
보다시피 프로세스를 공유받았다는 것은, 프로세스 디렉토리에 해당 정보가 표시된다는 말이다.
중간에 하늘색(소프트 링크 파일) root가 있는데, 저게 1번 프로세스의 루트 파일시스템을 나타내는 파일이다.
chroot /proc/1/root /bin/bash
그러면 아예 루트 디렉토리를 바꾸는 명령어로 distroless 이미지의 파일시스템을 볼 수 있게 된다.
그런데 그렇게 해도 ls
, cat
등의 명령어가 없어서 안 속을 뜯어보는 것은 쉬운 일이 아니다.
근데... 굳이 루트 디렉토리를 바꿀 이유가 없다 ㅋㅋ
자, 이제 슬슬 보고 싶은 것들이 보이기 시작한다.
위에서 봤던, SDS api를 주고받는다는 소켓 파일을 찾아냈다.
ss -xep
소켓 파일 정보는 명령어로도 볼 수 있다.
이렇게보면 엔보이와 에이전트가 어떻게 통신하고 있는지가 조금 더 선명하게 확인된다.
하는 김에 컨트롤 플레인 통신 경로도 추적해보기
스터디를 처음 시작할 때 아주 간단하게 프록시 컨테이너를 뜯어보았다.
그때도 소켓파일을 보았는데, 이제 머리가 조금 굵어졌으니 명확하게 어떻게 구조가 이뤄져있는 건지도 추적해보자.
이전에도 말했지만 컨트롤 플레인에서는 15012 포트를 통해 각종 설정을 노출해주고 있다.
그런데 사실 해당 포트와 통신을 하고 있는 것은, 엔보이가 아니라 파일럿 에이전트이다.
그렇다면 에이전트는 어떻게 엔보이에게 실질적인 설정들을 전달해줄 수 있을까?
에이전트는 이렇게 두 가지 유닉스 소켓 파일을 열어두고 있는데, 엔보이 설정에 이게 어떻게 들어있는지를 보면 된다.
istioctl pc bootstrap debug
엔보이 부트스트랩 파일을 보면 dynamicResource에 ADS 설정을 받을 때 xds-grpc라는 클러스터로 연결하는 것을 알 수 있는데,
해당 클러스터는 사실 에이전트가 열고 있는 소켓 파일이다.
SDS 설정을 받아올 때 사용되는 클러스터 역시 마찬가지이다.
왜 이런 구조로 돼있는가 처음에는 의아했는데, 파일 디스크립터를 보고 살짝 예상이 되는 부분이 있었다.
파일럿 에이전트는 엔보이 프록시가 동작하기 이전에 환경을 세팅할 책임을 가지고 먼저 소켓 파일을 만들어둔다.
컨트롤 플레인과 실제로 통신을 하는 시점은 소켓 파일 생성 시점보다 늦는데, 이는 에이전트가 엔보이 설정을 위한 프록시로서 동작하고 있다는 것을 명확하게 보여준다.
파일럿 에이전트가 엔보이를 위한 프록시로서 동작하여 얻는 이점은 두 가지가 있다고 생각한다.
- 컨트롤 플레인과의 연결이 불안정하더라도 그러한 장애의 전파가 엔보이로 전달되지 않도록 브레이킹 역할을 할 수 있다.
- 엔보이가 설정 동기화됐는지 확인하기 위해 에이전트가 15020 관리용 포트로 정보를 노출하는데, 애초에 에이전트를 거쳐 설정이 동기화된다면 이러한 정보를 명확하게 인지하고 노출해줄 수 있다.
토큰 확인
이번에는 이스티오를 위해 발급된 jwt 토큰을 보자.
이 토큰은 어디까지나 api서버가 발급한다는 것에 유의하자.
이 jwt 토큰의 청중 대상은 istio-ca, 즉 istiod 내부의 인증 모듈(과거 Citadel)이다.
파드에 사이드카가 주입될 당시 변형되는 양식을 보면, 사이드카가 사용할 볼륨이 projected volume 형태로 들어가는 것을 확인할 수 있다.
이게 위 토큰인데, 이렇게 써있다고 이스티오가 토큰 생성에 관여하는 건 사실 전혀 아니다.
그냥 istiod는 토큰에 청중을 istio-ca
라고만 써주세요 api 서버에 부탁한 것에 불과하다.
결론
서비스 간 통신에서 암호화를 하게 되면 내부망에 침입한 공격자가 있더라도 패킷 탈취를 하는 것을 막을 수 있다.
이스티오에서는 그저 암호화를 하는데에 그치지 않고 상호 인증서를 배치하여 서로의 신원을 명확하게 확인할 수 있게 도와준다.
이걸 이용해서 아예 트래픽 인가 정책을 짤 수도 있는데, 그건 다음 노트에서 알아본다.
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - 서비스 메시와 이스티오 | 1 | published | 2025-04-10 |
1W - 간단한 장애 상황 구현 후 대응 실습 | 2 | published | 2025-04-10 |
1W - Gateway API를 활용한 설정 | 3 | published | 2025-04-10 |
1W - 네이티브 사이드카 컨테이너 이용 | 4 | published | 2025-04-10 |
2W - 엔보이 | 5 | published | 2025-04-19 |
2W - 인그레스 게이트웨이 실습 | 6 | published | 2025-04-17 |
3W - 버츄얼 서비스를 활용한 기본 트래픽 관리 | 7 | published | 2025-04-22 |
3W - 트래픽 가중치 - flagger와 argo rollout을 이용한 점진적 배포 | 8 | published | 2025-04-22 |
3W - 트래픽 미러링 패킷 캡쳐 | 9 | published | 2025-04-22 |
3W - 서비스 엔트리와 이그레스 게이트웨이 | 10 | published | 2025-04-22 |
3W - 데스티네이션 룰을 활용한 네트워크 복원력 | 11 | published | 2025-04-26 |
3W - 타임아웃, 재시도를 활용한 네트워크 복원력 | 12 | published | 2025-04-26 |
4W - 이스티오 메트릭 확인 | 13 | published | 2025-05-03 |
4W - 이스티오 메트릭 커스텀, 프로메테우스와 그라파나 | 14 | published | 2025-05-03 |
4W - 오픈텔레메트리 기반 트레이싱 예거 시각화, 키알리 시각화 | 15 | published | 2025-05-03 |
4W - 번외 - 트레이싱용 심플 메시 서버 개발 | 16 | published | 2025-05-03 |
5W - 이스티오 mTLS와 SPIFFE | 17 | published | 2025-05-11 |
5W - 이스티오 JWT 인증 | 18 | published | 2025-05-11 |
5W - 이스티오 인가 정책 설정 | 19 | published | 2025-05-11 |
6W - 이스티오 설정 트러블슈팅 | 20 | published | 2025-05-18 |
6W - 이스티오 컨트롤 플레인 성능 최적화 | 21 | published | 2025-05-18 |
8W - 가상머신 통합하기 | 22 | published | 2025-06-01 |
8W - 엔보이와 iptables 뜯어먹기 | 23 | published | 2025-06-01 |
9W - 앰비언트 모드 구조, 원리 | 24 | published | 2025-06-07 |
9W - 앰비언트 헬름 설치, 각종 리소스 실습 | 25 | published | 2025-06-07 |
7W - 이스티오 메시 스케일링 | 26 | published | 2025-06-09 |
7W - 엔보이 필터를 통한 기능 확장 | 27 | published | 2025-06-09 |
관련 문서
이름 | noteType | created |
---|---|---|
Cert Manager | knowledge | 2025-03-15 |
TLS downgrade attack | knowledge | 2024-06-20 |
openssl | knowledge | 2025-01-18 |
TLS | knowledge | 2025-04-16 |
PKI | knowledge | 2025-03-10 |
cfssl | knowledge | 2025-01-23 |
Istio PeerAuthentication | knowledge | 2025-05-04 |
SPIFFE | knowledge | 2025-05-04 |
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 | published | 2025-03-15 |
E-api 서버 감사 | topic/explain | 2025-01-21 |
E-이스티오 메시 스케일링 | topic/explain | 2025-06-08 |